using System;
using System.Collections.Generic;
using System.Text;
using ADODB;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Data.SqlClient;
using log4net;

namespace AgileJedi.MemcachedForMonorail
{
	public class MemCacheDB : IDisposable
	{
		/// <summary>
		/// Initializes a new instance of the MemCacheDB class.
		/// </summary>
		public MemCacheDB(bool ignoreDeSerializationErrors)
		{
			_ignoreDeserializationErrors = ignoreDeSerializationErrors;
		}

		#region Fields

		private ILog _logger = log4net.LogManager.GetLogger(typeof(MemCacheDB));

		private string cnStr = "Data Source=.\\SQLEXPRESS;Initial Catalog=MEMCACHED;Integrated Security=true;";

		private static int guidLength = Guid.Empty.ToString().Length;
		private const String persistSQL = @"IF NOT EXISTS (Select SessionId FROM SessionItems 
															WHERE SessionId = @SESSIONID_VAL AND ItemKey = @KEY_VAL)
												Insert Into [SessionItems] (Id, SessionId, [ItemKey], ItemData) 
												Values (NewId(), @SESSIONID_VAL, @KEY_VAL, @ITEMDATA_VAL)
											  ELSE
												UPDATE SessionItems
												SET ItemData = @ITEMDATA_VAL
												WHERE SessionId = @SESSIONID_VAL AND ItemKey = @KEY_VAL";
		private const String removeSQL = "Delete From SessionItems Where SessionId = @SESSIONID_VAL and [ItemKey] = @KEY_VAL";
		private const String retrieveSQL = "Select ItemData From SessionItems Where SessionId = @SESSIONID_VAL and [ItemKey] = @KEY_VAL";
		private const String removeSessionSQL = "Delete From SessionItems Where SessionId = @SESSIONID_VAL";
		private const String countSQL = "Select count(*) as ItemCount From SessionItems Where SessionId = @SESSIONID_VAL and [ItemKey] = @KEY_VAL";

		private SqlConnection cn;
		private SqlCommand persistCommand, removeCommand, retrieveCommand, clearSessionCommand, countCommand;
		private MemoryStream ms;
		private BinaryFormatter serializer = new BinaryFormatter();

		private bool _ignoreDeserializationErrors = false;
		#endregion
		#region Properties
		/// <summary>
		/// If set to true the DB Manager will return Null when a deserialization Error Occurs.
		/// </summary>
		public bool IgnoreDeserializationErrors
		{
			get
			{
				return _ignoreDeserializationErrors;
			}
			set
			{
				_ignoreDeserializationErrors = value;
			}
		}
		#endregion
        public void SetConnectionString(string cnStr)
		{
			this.cnStr = cnStr;
			SetupConnection();
		}

		public void InitParams()
		{
			persistCommand = new SqlCommand(persistSQL);
			persistCommand.Parameters.Add(new SqlParameter("SESSIONID_VAL", System.Data.SqlDbType.UniqueIdentifier, 36));
			persistCommand.Parameters.Add(new SqlParameter("KEY_VAL", System.Data.SqlDbType.VarChar, 50));
			persistCommand.Parameters.Add(new SqlParameter("ITEMDATA_VAL", System.Data.SqlDbType.Image, int.MaxValue));
			persistCommand.CommandType = System.Data.CommandType.Text;
			

			removeCommand = new SqlCommand(removeSQL);
			removeCommand.Parameters.Add(new SqlParameter("SESSIONID_VAL", System.Data.SqlDbType.UniqueIdentifier, 36));
			removeCommand.Parameters.Add(new SqlParameter("KEY_VAL", System.Data.SqlDbType.VarChar, 50));
			

			retrieveCommand = new SqlCommand(retrieveSQL);
			retrieveCommand.Parameters.Add(new SqlParameter("SESSIONID_VAL", System.Data.SqlDbType.UniqueIdentifier, 36));
			retrieveCommand.Parameters.Add(new SqlParameter("KEY_VAL", System.Data.SqlDbType.VarChar, 50));
			

            countCommand = new SqlCommand(countSQL);
            countCommand.Parameters.Add(new SqlParameter("SESSIONID_VAL", System.Data.SqlDbType.UniqueIdentifier, 36));
            countCommand.Parameters.Add(new SqlParameter("KEY_VAL", System.Data.SqlDbType.VarChar, 50));
            

			clearSessionCommand = new SqlCommand(removeSessionSQL);
			clearSessionCommand.Parameters.Add(new SqlParameter("SESSIONID_VAL", System.Data.SqlDbType.UniqueIdentifier, 36));
			
		}

		public void SetupConnection()
		{
			if (cn != null)
				CloseConnection();
			cn = new SqlConnection(cnStr);
			cn.Open();
			InitParams();
		}

		public void CloseConnection()
		{
			if (cn != null)
			{
				cn.Close();
				cn = null;
			}
		}

		private void ClearState()
		{
			ms = null;
		}

		public void PersistRecord(Guid sessionId, String key, object value)
		{
            /// TODO: Batch these commands!!!!
			using (SqlConnection connection = new SqlConnection(this.cnStr))
			{
				connection.Open();
				SqlCommand cmd = persistCommand.Clone();
                
				cmd.Connection = connection;
				cmd.Prepare();
				try
				{

					if (cmd.Connection.State == System.Data.ConnectionState.Closed)
					{
						cmd.Connection.Open();
					}
                    
                    
					ms = new MemoryStream();
					serializer.Serialize(ms, value);
					ms.Position = 0;
					cmd.Parameters[0].Value = sessionId;
					cmd.Parameters[1].Value = key;
					cmd.Parameters[2].Value = ms.ToArray();
					cmd.ExecuteNonQuery();
				}
				catch (Exception ex)
				{
					throw new System.Data.DataException(String.Format("Failed to persist record {0} for session {1}", key, sessionId), ex);
				}
				finally
				{
					cmd.Connection.Close();
				}
			}
		}

		public void RemoveRecord(Guid sessionId, string key)
		{
			using (SqlConnection connection = new SqlConnection(this.cnStr))
			{
				connection.Open();
				SqlCommand cmd = removeCommand.Clone();				
				cmd.Connection = connection;
				cmd.Prepare();
				
				if (cmd.Connection.State == System.Data.ConnectionState.Closed)
				{
					cmd.Connection.Open();
				}
				cmd.Parameters[0].Value = sessionId;
				cmd.Parameters[1].Value = key;
				cmd.ExecuteNonQuery();
				
			}
		}

		public object RetrieveRecord(Guid sessionId, string key)
		{

			using (SqlConnection connection = new SqlConnection(this.cnStr))
			{
				connection.Open();
				SqlDataReader reader = null;

				SqlCommand cmd = retrieveCommand.Clone();
				cmd.Connection = connection;
				cmd.Prepare();

				try
				{
					cmd.Parameters[0].Value = sessionId;
					cmd.Parameters[1].Value = key;

					reader = cmd.ExecuteReader();
					if (reader != null && !reader.IsClosed && reader.HasRows && reader.Read())
					{
						Object item = reader["ItemData"];
						if (item is byte[])
						{
							ms = new MemoryStream((byte[])item);
							return serializer.Deserialize(ms);
						}
						else if (item is DBNull)
							return null;
						else
							throw new Exception(String.Format("Cannot caste type '{0}' to byte[]", item.GetType()));
					}
					else
					{
						return null;
					}
				}
				catch (System.Runtime.Serialization.SerializationException sex)
				{
					_logger.Fatal(String.Format("Serialization error occured while trying to retrieve key {0} for session {1}", key, sessionId), sex);
					if (this.IgnoreDeserializationErrors)
					{
						_logger.WarnFormat("Ignoring th Deserialization error and sending back null for key {0}", key);
						return null;
					}
					else
						throw sex;
				}
				catch (Exception ex)
				{
					_logger.Fatal(String.Format("Unexpected error occured while trying to retrieve key {0} for session {1}", key, sessionId), ex);
					throw ex;
				}
				finally
				{
					if (reader != null)
					{
						if (!reader.IsClosed)
							reader.Close();

						reader.Dispose();
					}
				}
			}
		}

        public bool RecordExists(Guid sessionId, string key)
        {

            using (SqlConnection connection = new SqlConnection(this.cnStr))
            {
                connection.Open();

                SqlCommand cmd = countCommand.Clone();
                cmd.Connection = connection;
                cmd.Prepare();

                try
                {
                    int count = 0;
                    cmd.Parameters[0].Value = sessionId;
                    cmd.Parameters[1].Value = key;


                    count = (int)cmd.ExecuteScalar();
                    return count > 0;
                    
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                
            }
        }

		public void RemoveSession(Guid sessionId)
		{
			using (SqlConnection connection = new SqlConnection(this.cnStr))
			{
				connection.Open();
				SqlCommand cmd = clearSessionCommand.Clone();
				cmd.Connection = connection;
				cmd.Prepare();
				try
				{
					if (cmd.Connection.State == System.Data.ConnectionState.Closed)
					{
						cmd.Connection.Open();
					}

					cmd.Parameters[0].Value = sessionId;
					cmd.ExecuteNonQuery();
				}
				finally
				{
					cmd.Connection.Close();
				}
			}
		}
				

		#region IDisposable Members
		public void Dispose()
		{
			CloseConnection();
			ClearState();
		}
		#endregion
	}
}